using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;

namespace Slaks.Web.Parser
{
	public class ParserBasics
	{
   
		/// <summary>
		/// <para>
		/// User defined html structures (xml source file)
		/// </para>
		/// <para>
		/// Xml user defined structure file :
		/// </para>
		/// <code>
		/// <structures>
		///    <structure name="TABLE" startTag="table" endTag="/table"/>
		///    <structure name="FORM" startTag="form" endTag="/form"/>
		///    <structure name="SELECT" startTag="select" endTag="/select"/>
		///    <structure name="FRAMESET" startTag="frameset" endTag="/frameset"/>
		/// </structures>
		/// </code>
		/// </summary>
		protected Utils.Structure[] m_structsToConsider = null;

		/// <summary>
		/// Name of the xml user defined structure file
		/// </summary>
		protected string m_structureXmlFile = null;

		/// <summary>
		/// Direction of search pattern, used by several internal functions
		/// </summary>
		protected enum FindDirection { Right, Left };

		/// <summary>
		/// Indicates the source of the html file (web or local)
		/// </summary>
		protected enum ReadSource { Web, Local }

		/// <summary>
		/// File name of the source
		/// </summary>
		protected string m_fileName;

		/// <summary>
		/// Encoding used by the source html file
		/// </summary>
		protected Encoding m_encoding;

		/// <summary>
		/// internal index within the source file
		/// </summary>
		protected int m_index = 0;

		/// <summary>
		/// buffer length of the source file
		/// </summary>
		protected int m_bufferLength;


		/// <summary>
		/// the buffer of the source file
		/// </summary>
		protected string m_buffer;


		/// <summary>
		/// type of the source (web or local)
		/// </summary>
		protected ReadSource m_source;

		/// <summary>
		/// basic uri address from which the source came
		/// </summary>
		protected Uri m_baseUri;

		/// <summary>
		/// location of the current link address
		/// Example: the link was retrieved from web page www.mail.ru/index.html, so
		/// the m_addressLinkLocation is http://mail.ru/index.html
		/// </summary>
		protected Uri m_addressLinkLocation = null;

		protected static string PatternUnicode = "&#(\\d{1,4});";

		protected ParserBasics() {}

    /// <summary>
    /// Static constructor
    /// </summary>
		static ParserBasics()
		{
		}
	
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="filename">name of the file (http://....) for online internet site else it will be treated as local</param>
		/// <param name="encoding">encoding of the file</param>
    protected ParserBasics(string filename,Encoding encoding)
    {
      m_fileName = filename;
      m_encoding = encoding;

      if (filename.StartsWith("http"))
      {
        m_source = ReadSource.Web;
        m_baseUri = new Uri(filename);

      }
      else 
      {
        m_source = ReadSource.Local;
				if (m_fileName.StartsWith("file:///"))
				{
					m_fileName = m_fileName.Substring(8);
				}

				m_fileName = Regex.Replace(m_fileName,"%20"," ");
				m_baseUri = new Uri(m_fileName);
      }
			m_addressLinkLocation = new Uri("http://localhost");
    }

		/// <summary>
		/// Constructor
		/// Better used for internet online parsing
		/// Gets additional parameter "addressLinkLocation
		/// </summary>
		/// <param name="internetPath">the internet path to the given web page that must be parsed</param>
		/// <param name="addressLinkLocation">parent location of the web page to be parsed</param>
		/// <param name="encoding"></param>
		protected ParserBasics(string internetPath, string addressLinkLocation,Encoding encoding)
		{
			m_fileName = internetPath;
			try
			{
				m_addressLinkLocation = new Uri(addressLinkLocation);
			}
			catch (Exception)
			{
				m_addressLinkLocation = new Uri("http://localhost");
			}
			m_encoding = encoding;

			if (internetPath.StartsWith("http"))
			{
				m_source = ReadSource.Web;
			}
			else
			{
				m_source = ReadSource.Local;
				if (m_fileName.StartsWith("file:///"))
				{
					m_fileName = m_fileName.Substring(8);
				}
				m_fileName = Regex.Replace(m_fileName,"%20"," ");
			}

			m_baseUri = new Uri(internetPath);
		}

		/// <summary>
		/// Get or set name of the xml user defined structure file
		/// </summary>
		public string ImportantStructureXmlFile
		{
			get { return m_structureXmlFile; }
			set { m_structureXmlFile = value; }
		}

		/// <summary>
		/// Enforce using the xml user defined structures
		/// </summary>
		/// <param name="isUse">true if used, false if not used</param>
		public void UseImportantStructuresFromXmlFile(bool isUse)
		{
			if (isUse == false) 
			{
				m_structsToConsider = null;
			}
			else
			{
				try
				{
					m_structsToConsider = Utils.Structure.ReadXml(new StreamReader(m_structureXmlFile));
				}
				catch(Exception)
				{
					m_structsToConsider = null;
				}
			}
	  }

    /// <summary>
    /// Unparsed HTML file
    /// </summary>
    public string RawHtml
    {
      get { return m_buffer; }
    }
		

		/// <summary>
		/// Check whether the end of file reached
		/// </summary>
		/// <returns></returns>
    protected bool Eof()
    {
      return Eof(1);
    }

		/// <summary>
		/// Check whether the end of file reached by providing position relative to current index (value can be negative)
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
    protected bool Eof(int position)
    {
      if (position > 0)
      return (m_bufferLength < m_index + position);

      return (0 > m_index + position);
    }

    /// <summary>
    /// Advance the index of the buffer by one position
    /// </summary>
    protected void AdvanceIndex()
    {
      AdvanceIndex(1);
    }

		/// <summary>
		/// Advance the index of the buffer by specifying the position
		/// </summary>
		/// <param name="position"></param>
    protected void AdvanceIndex(int position)
    {
      m_index += position;
    }

		/// <summary>
		/// Current char under current position
		/// </summary>
		/// <returns></returns>
    protected char CurrentChar()
    {
      return CurrentChar(0);
    }

		/// <summary>
		/// Current char under specified position
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
    protected char CurrentChar(int position)
    {
      return char.ToLower(m_buffer[m_index + position]);
    }

		/// <summary>
		/// Compare character
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns>
		protected bool Compare(char text)
		{
      return CurrentChar(0) == text;
		}

		/// <summary>
		/// Compare
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns>
    protected bool Compare(string text)
    {
      return Compare(text,0,FindDirection.Right);
    }

		protected bool Compare(string text,int relativePosition, FindDirection direction)
		{
			switch(direction)
			{
				case FindDirection.Right:
					  if (Eof(relativePosition + text.Length - 2)) return false;
					  
					  for (int i=relativePosition;i<text.Length;i++)
					  {
						  if (!Eof(relativePosition))
						  {
                if (!CurrentChar(relativePosition).Equals(text[i])) return false;
							  ++relativePosition;
						  }
						  else break;
 					  }
					  return true;
				case FindDirection.Left:
					  if (Eof(relativePosition - text.Length + 1)) return false;

					  for (int i=text.Length - 1;i>=0;i--)
					  {
							if (!CurrentChar(relativePosition).Equals(text[i])) return false;
							--relativePosition;
					  }
					  return true;
				default : return false;
			}//end of switch
		}//enf of function

		/// <summary>
		/// Current index of the file
		/// </summary>
    protected int CurrentIndex
    {
      get { return m_index; }
    }

		/// <summary>
		/// Skip the white spaces
		/// </summary>
    protected void SkipWhiteSpace()
    {
      while (!Eof())
      {
        if (this.Compare(' ') || this.Compare('\t')) AdvanceIndex();
        else break;
      }
    }


		/// <summary>
		/// skip some text until encounter of the specified endChar
		/// <see>
		///  SkipUntilPatternFound
		/// </see>
		/// </summary>
		/// <param name="endChar">the character marking the end of skipping</param>
    protected void SkipUntilEndOf(char endChar)
    {
      while (!Eof())
      {
        if (CurrentChar() != endChar) AdvanceIndex();
        else { break; }
      }
    }

		/// <summary>
		/// Read text until non letter encountered
		/// Main purpose to read the tag
		/// Explanation : upon encounter of the symbol "<" apply ReadUntilNonLetter to read the text.
		/// </summary>
		/// <returns></returns>
		protected string ReadTag()
		{
			StringBuilder tag = new StringBuilder();

			//read the first charater after '<'
			char ch = CurrentChar();
			tag.Append(ch);
			this.AdvanceIndex();

			while (!Eof())
			{
				ch = CurrentChar();
				if (char.IsLetterOrDigit(ch)) tag.Append(ch);
				else break;
				this.AdvanceIndex();
			}
			return tag.ToString();
		}

		/// <summary>
		/// Find the pattern by specifying representative character and direction of the comparison
		/// Example:
		///  <script> was found and it is nessesary to skip it (because it is irrelevant)
		///  The pattern can be "/script>" and direction to the right FindDirection.Left
		///  The function will find "/" and will compare the "script>".
		///  The same could be achieved by specifying the pattern "/script> , representative character 
		///  ">" and direction to the left FindDirection.Left
		///  Note : the representative character must be the part of the pattern (start or end)
		///  In order to find one character apply FindDirection.Left
		/// </summary>
		/// <param name="pattern"></param>
		/// <param name="representativeChar"></param>
		/// <param name="direction"></param>
		protected void SkipUntilPatternFound(string pattern,char representativeChar,FindDirection direction)
		{
			while (!Eof())
			{
				if (CurrentChar() == representativeChar)
				{
					//right comparison
					if (direction == FindDirection.Right)
					{
						if (this.Compare(pattern,1,FindDirection.Right)) 
						{
							AdvanceIndex(pattern.Length - 1);
							return;
						}
					}
					else
					{
						if (this.Compare(pattern,0,FindDirection.Left)) return;
					}
				}//end of if

				if (Eof(2)) break;
				AdvanceIndex();

			}//end of while
		}//end of Skip


    /// <summary>
    /// Get the text between current index and the end of the pattern
    /// <see>
    /// SkipUntilPatternFound
    /// </see>
    /// </summary>
    /// <param name="pattern"></param>
    /// <param name="representativeChar"></param>
    /// <param name="direction"></param>
    /// <returns></returns>
    protected string ReadUntilPatternFound(string pattern,char representativeChar,FindDirection direction)
    {
      int startIndex = this.CurrentIndex;

      this.SkipUntilPatternFound(pattern,representativeChar,direction);

      int lengthOfSubstring = this.CurrentIndex - startIndex + 1;

      string text = this.RawHtml.Substring(startIndex,lengthOfSubstring);

      return text;
    }

		/// <summary>
		/// if the character is encoded as unicode &#1234; return the character
		/// else return 0;
		/// </summary>
		/// <param name="unicode">&#1234; like string or &leq; (not a character)</param>
		/// <returns></returns>
		protected internal static char GetUnicodeChar(string unicode)
		{
			Match m = Regex.Match(unicode,PatternUnicode);
			if (m.Success) 
			{
				GroupCollection gc = m.Groups;
				Group g = gc[1];
				char ch = (char)int.Parse(g.Value);
				if (ch == 34 ||
					  ch == 39 ||
					  ch == 38 ||
					  ch == 60 ||
					  ch == 62 ||
					  ch == 149 ||
					  (ch >= 160 && ch <= 255) ||
					  ch == 338 ||
					  ch == 339 ||
					  ch == 352 ||
					  ch == 353 ||
					  ch == 376 ||
					  ch == 710 ||
					  ch == 732 ||
					  ch == 8194 ||
					  ch == 8195 ||
					  ch == 8201 ||
					  (ch >= 8204 && ch <= 8207) ||
					  ch == 8211 ||
					  ch == 8212 ||
					  (ch >= 8216 && ch <= 8218) ||
					  ch == 8220 ||
					  ch == 8221 ||
					  ch == 8222 ||
					  ch == 8224 ||
					  ch == 8225 ||
					  ch == 8226 ||
					  ch == 8230 ||
					  ch == 8240 ||
					  ch == 8249 ||
					  ch == 8250 ||
					  ch == 8364 ||
					  ch == 8482) return (char)0;
					  
				return ch;
			}
			return (char)0;
		}

    /// <summary>
    /// Some characters are defined as punctuation
    /// </summary>
    /// <param name="ch">the character</param>
    /// <returns>true/false</returns>
    protected internal static bool IsPunctuationCharacter(char ch)
    {
			return (ch == 0x07c);
			/*
      return (ch == 0x021 || 
				      ch == 0x22  ||
				      ch == 0x27  ||
              ch == 0x028 || 
				      ch == 0x029 || 
				      ch == 0x02c ||
              ch == 0x03b ||
				      ch == 0x60  ||
				      ch == 0x07c);
							*/
    }

    /// <summary>
    /// Scan the text for special entity characters or unicode
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    protected internal static string ScanTextForSpecialEntityCharacters(string text)
    {
      string temp = String.Empty;
      string entity = String.Empty;
      for (int i=0;i<text.Length;i++)
      {
        if (text[i] != '&') temp+=text[i];
        else
        {
          int j=0;
          for (j=i;j<text.Length;j++)
          {
            entity += text[j];
            if (text[j] == ';') break;
          }
          char unicodeChar = DocumentHtmlParser.GetUnicodeChar(entity);
          if (unicodeChar != 0) temp += unicodeChar;
					else temp+=' ';

          entity = String.Empty;
          i=j;
        }
      }//end of for
      return temp;
    }

    /// <summary>
    /// Get the page from Local storage or from internet
    /// </summary>
    /// <returns></returns>
    protected bool GetWebPage()
    {
			m_index = 0;
      try
      {
        switch(m_source)
        {
          case ReadSource.Local:
              m_buffer = Utils.LocalPageDownload.GetWebPage(m_fileName,m_encoding);
              if (m_buffer == null) return false;
            break;
          case ReadSource.Web:
              m_buffer = Utils.WebPageDownload.GetWebPage(m_fileName,ref m_encoding);
             if (m_buffer == null) return false;
            break;
        }
      }
      catch(Exception ex)
      {
        Console.WriteLine(ex.Message);
        return false;
      }
      m_bufferLength = m_buffer.Length;
      return true;
    }
  }//end of class
}//end of namespace
